﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGinnBPM.Services;
using NGinnBPM.Lib.Schema;
using NGinnBPM.Lib.Data;
using NGinnBPM.Lib.Interfaces;
using NGinnBPM.Lib.Exceptions;
using System.IO;
using Rhino.DSL;
using NLog;
using System.ComponentModel;

namespace NGinnBPM.Services.Scripting.BooScript
{
    public class BooProcessScriptManager : IProcessScriptManager, ISupportInitialize
    {
        private string _baseDir;
        private Logger log = LogManager.GetCurrentClassLogger();
        private Dictionary<string, DslFactory> _factories = new Dictionary<string, DslFactory>();

        

        #region IProcessScriptManager Members

        public BooProcessScriptManager()
        {

        }

        public void Initialize()
        {
            if (Directory.Exists(BaseDirectory))
            {
                foreach (string dir in Directory.GetDirectories(BaseDirectory))
                {
                    try
                    {
                        Directory.Delete(dir, true);
                    }
                    catch (Exception ex) { log.Warn("Error deleting {0}: {1}", dir, ex); }
                }
            }
        }


        public string BaseDirectory
        {
            get { return _baseDir; }
            set { _baseDir = value; }
        }

        public void ProcessDefinitionUpdated(ProcessDefinition pd)
        {
            throw new NotImplementedException();
        }

        public void PackageDefinitionUpdated(PackageDefinition pd)
        {
            log.Warn("Package definition updated: {0}", pd.Name);
            DslFactory fact = CreatePackageScriptFactory(pd);
            foreach (ProcessDefinition def in pd.ProcessDefinitions)
            {
                fact.Create<ScriptBase>(GetProcessScriptClassName(def.LocalName) + ".boo");
            }
            lock (this)
            {
                if (_factories.Remove(pd.Name))
                {
                    log.Info("Removed cached package script {0}", pd.Name);
                }
                _factories[pd.Name] = fact;
                log.Info("Cached factories: " + _factories.Count);
            }
        }

        protected DslFactory CreatePackageScriptFactory(PackageDefinition pd)
        {
            int cnt = 1;
            string dir;
            do
            {
                dir = Path.Combine(BaseDirectory, pd.Name + "_" + cnt++);
            } while (Directory.Exists(dir));

            log.Info("Creating script factory for package {0} in {1}", pd.Name, dir);
            if (Directory.Exists(dir))
                Directory.Delete(dir, true);
            Directory.CreateDirectory(dir);
            
            DslFactory fact = new DslFactory();
            fact.BaseDirectory = dir;
            foreach (ProcessDefinition def in pd.ProcessDefinitions)
            {
                log.Info("Creating script file for process {0}/{1}", pd.Name, def.LocalName);
                GenerateProcessScript(dir, def);
            }
            ProcessScriptDslEngine eng = new ProcessScriptDslEngine(delegate(string url)
            {
                string s = System.IO.Path.GetFullPath(System.IO.Path.Combine(fact.BaseDirectory, url));
                log.Trace("Normalized {0} is {1}", url, s);
                return s;
            });
            fact.Register<ScriptBase>(eng);
            fact.Compilation += new EventHandler(fact_Compilation);
            fact.Recompilation += new EventHandler(fact_Recompilation);
            return fact;
        }

        void fact_Recompilation(object sender, EventArgs e)
        {
            log.Warn("Recompilation in {0}", this.BaseDirectory);
        }

        void fact_Compilation(object sender, EventArgs e)
        {
            log.Warn("Compilation in {0}", this.BaseDirectory);
        }
        

        public ITaskScript GetTaskScript(ProcessDefinition pd, string taskId)
        {
            try
            {
                DslFactory fact;
                lock (this)
                {
                    if (!_factories.TryGetValue(pd.Package.Name, out fact))
                    {
                        fact = CreatePackageScriptFactory(pd.Package);
                        _factories[pd.Package.Name] = fact;
                        log.Info("Cached factories: " + _factories.Count);
                    }
                }

                ScriptBase sb = fact.Create<ScriptBase>(GetProcessScriptClassName(pd.LocalName) + ".boo", null);
                if (taskId == null)
                    taskId = pd.Body.Id;
                sb.TaskId = taskId;
                sb.Initialize();
                return sb;
            }
            catch (Boo.Lang.Compiler.CompilerError ex)
            {
                log.Warn("Process script compilation error in {0}/{1}: {2}: {3}", pd.DefinitionId, taskId, ex.Code, ex);
                string s = string.Format("Process script error: {0}", ex.Message);
                throw new ProcessScriptCompilationError(s).SetTaskId(taskId).SetProcessDef(pd.DefinitionId);
            }
            catch (Exception ex)
            {
                log.Error("Error getting script for task {0} in process {1}: {2}", taskId, pd.DefinitionId, ex);
                throw ex;
            }
        }

        #endregion

        public static string GetProcessScriptClassName(string definitionId)
        {
            return string.Format("BooScript_{0}", definitionId.Replace('.', '_'));
        }

        public static string GetInputVariableBindingAlias(string taskId, string varName)
        {
            return string.Format("InputBinding_{0}_{1}", taskId, varName);
        }

        public static string GetOutputVariableBindingAlias(string taskId, string varName)
        {
            return string.Format("OutputBinding_{0}_{1}", taskId, varName);
        }

        public static string GetDefaultVariableValueAlias(string taskId, string varName)
        {
            return string.Format("DefaultVariable_{0}_{1}", taskId, varName);
        }

        public static string GetMultiInstanceSplitQueryAlias(string taskId)
        {
            return string.Format("MultiSplitQuery_{0}", taskId);
        }

        public static string GetFlowAlias(Flow fl)
        {
            if (fl.SourcePort != TaskOutPortType.Default) throw new Exception(); //
            return string.Format("{0}:{1}->{2}", fl.SourcePort, fl.From.Id, fl.To.Id);
        }

        public static string GetTaskCodeBlockAlias(string taskId, string blockId)
        {
            return string.Format("TaskCode_{0}_{1}", taskId, blockId);
        }

        public static string GetTaskInputParameterBindingAlias(string taskId, string paramName)
        {
            return string.Format("InputParameter_{0}_{1}", taskId, paramName);
        }

        public static string GetTaskOutputParameterBindingAlias(string taskId, string variableName)
        {
            return string.Format("OutputParameter_{0}_{1}", taskId, variableName);
        }

        /*
        public static string GetTaskOnEnableScriptAlias(string taskId)
        {
            return string.Format("OnEnableScript_{0}", taskId);
        }

        public static string GetTaskOnCompleteScriptAlias(string taskId)
        {
            return string.Format("OnCompleteScript_{0}", taskId);
        }
        
        */


        protected void GenerateProcessScript(string dir, ProcessDefinition pd)
        {
            string cname = GetProcessScriptClassName(pd.LocalName);

            string path = Path.Combine(dir, cname + ".boo");
            
            TextWriter sw = new StreamWriter(path,false, Encoding.UTF8);
            ScriptedClassGenerator sg = new ScriptedClassGenerator(sw);
            sg.AddImportNamespace("NGinnBPM.Lib.Data");
            sg.AddImportNamespace("NGinnBPM.Lib.Interfaces");
            sg.AddImportNamespace("NGinnBPM.Lib.Schema");
            sg.AddImportNamespace("NGinnBPM.Runtime");
            sg.AddImportNamespace("NGinnBPM.Services");
            sg.AddImportNamespace("NGinnBPM.Lib.Exceptions");
            sg.AddImportNamespace("NGinnBPM.Services.Scripting.BooScript");
            sg.AddImportNamespace("System");
            sg.AddImportNamespace("NLog");
            sg.StartClass(cname, "NGinnBPM.Services.Scripting.BooScript.ScriptBase");
            GenerateTaskScriptRecursive(pd.Body, sg);
            sg.EndClass();
            sw.Flush();
            sw.Close();
        }

        private void GenerateTaskScriptRecursive(Task tsk, ScriptedClassGenerator gen)
        {
            foreach (VariableBinding vb in tsk.InputBindings)
            {
                if (vb.BindType == VariableBindingType.Expr)
                {
                    string alias = GetInputVariableBindingAlias(tsk.Id, vb.VariableName);
                    gen.AddExpression(alias, vb.Expression);
                }
            }

            foreach (VariableBinding vb in tsk.InputParameterBindings)
            {
                if (vb.BindType == VariableBindingType.Expr)
                {
                    string alias = GetTaskInputParameterBindingAlias(tsk.Id, vb.VariableName);
                    gen.AddExpression(alias, vb.Expression);
                }
            }

            foreach (VariableBinding vb in tsk.OutputParameterBindings)
            {
                if (vb.BindType == VariableBindingType.Expr)
                {
                    string alias = GetTaskOutputParameterBindingAlias(tsk.Id, vb.VariableName);
                    gen.AddExpression(alias, vb.Expression);
                }
            }

            foreach (VariableBinding vb in tsk.OutputBindings)
            {
                if (vb.BindType == VariableBindingType.Expr)
                {
                    string alias = GetOutputVariableBindingAlias(tsk.Id, vb.VariableName);
                    gen.AddExpression(alias, vb.Expression);
                }
            }

            foreach (VariableDef vd in tsk.Variables)
            {
                if (vd.DefaultValueExpr != null && vd.DefaultValueExpr.Length > 0)
                {
                    string alias = GetDefaultVariableValueAlias(tsk.Id, vd.Name);
                    gen.AddExpression(alias, vd.DefaultValueExpr);
                }
            }

            if (tsk.IsMultiInstance)
            {
                string alias = GetMultiInstanceSplitQueryAlias(tsk.Id);
                gen.AddExpression(alias, tsk.MultiInstanceSplitExpression);
            }

            foreach (Flow fl in tsk.FlowsOut)
            {
                if (fl.InputCondition != null && fl.InputCondition.Length > 0)
                {
                    string alias = GetFlowAlias(fl);
                    gen.AddExpression(alias, fl.InputCondition);
                }
            }

            if (tsk.BeforeEnableScript != null && tsk.BeforeEnableScript.Length > 0)
            {
                string alias = GetTaskCodeBlockAlias(tsk.Id, "BeforeEnableScript"); 
                gen.AddCodeBlock(alias, tsk.BeforeEnableScript);
            }

            if (tsk.AfterEnableScript != null && tsk.AfterEnableScript.Length > 0)
            {
                string alias = GetTaskCodeBlockAlias(tsk.Id, "AfterEnableScript");
                gen.AddCodeBlock(alias, tsk.AfterEnableScript);
            }

            if (tsk.BeforeCompleteScript != null && tsk.BeforeCompleteScript.Length > 0)
            {
                string alias = GetTaskCodeBlockAlias(tsk.Id, "BeforeCompleteScript");
                gen.AddCodeBlock(alias, tsk.BeforeCompleteScript);
            }

            if (tsk is CompositeTask)
            {
                CompositeTask ct = (CompositeTask)tsk;
                foreach (Task t2 in ct.GetTasks())
                {
                    GenerateTaskScriptRecursive(t2, gen);
                }
            }
        }

        #region ISupportInitialize Members

        public void BeginInit()
        {
            Initialize();
        }

        public void EndInit()
        {
        }

        #endregion

        #region IProcessScriptManager Members


        public void VerifyPackageScripts(PackageDefinition pd)
        {
            DslFactory fact = CreatePackageScriptFactory(pd);
            throw new NotImplementedException("sHOULD NOT BE IMPLEMENTED");
        }

        #endregion
    }
}
